import os
os.environ['OPENCV_IO_ENABLE_OPENEXR'] = '1'
from pathlib import Path
import sys
if (_package_root := str(Path(__file__).absolute().parents[2])) not in sys.path:
    sys.path.insert(0, _package_root)
from typing import *
import itertools
import json
import warnings

import cv2
import numpy as np
import torch
from PIL import Image
from tqdm import tqdm
import trimesh
import trimesh.visual
import click

from moge.model.v1 import MoGeModel
from moge.utils.io import save_glb, save_ply
from moge.utils.vis import colorize_depth, colorize_normal
import utils3d
from transformers import  Mask2FormerForUniversalSegmentation,AutoImageProcessor
import pdb 
from PIL import Image
import numpy as np 
from os.path import join, split,exists


from dataset import *


    


def extract_object_mask2former(processor, model, image, device):
    """Extract foreground objects using Mask2Former, treating sky (class 10) as background"""
    # Process image through Mask2Former
    inputs = processor(images=image, return_tensors="pt").to(device)
    
    with torch.no_grad():
        outputs = model(**inputs)
    
    # Get semantic segmentation map
    predicted_semantic_map = processor.post_process_semantic_segmentation(outputs, target_sizes=[image.size[::-1]])[0]
    
    # Create a mask where classes 8, and 10 are considered as background (0), and everything else is foreground (1)
    mask_np = ~np.isin(predicted_semantic_map.cpu().numpy(), [8, 10])
    mask_np = mask_np.astype(np.uint8)
    
    # Create a white background image
    white_background = np.ones((*image.size[::-1], 3), dtype=np.uint8) * 255
    image_np = np.array(image)
    
    # Apply the mask
    masked_image = np.where(mask_np[:,:,np.newaxis] > 0, image_np, white_background)
    
    # Convert back to PIL Image
    masked_image_pil = Image.fromarray(masked_image)
    
    return masked_image_pil, mask_np




@click.command(help='Inference script')
@click.option('--input', '-i', 'input_path', type=click.Path(exists=True), help='Input image or folder path. "jpg" and "png" are supported.')
@click.option('--fov_x', 'fov_x_', type=float, default=None, help='If camera parameters are known, set the horizontal field of view in degrees. Otherwise, MoGe will estimate it.')
@click.option('--output', '-o', 'output_path', default='./output', type=click.Path(), help='Output folder path')
@click.option('--pretrained', 'pretrained_model_name_or_path', type=str, default='Ruicheng/moge-vitl', help='Pretrained model name or path. Defaults to "Ruicheng/moge-vitl"')
@click.option('--device', 'device_name', type=str, default='cuda', help='Device name (e.g. "cuda", "cuda:0", "cpu"). Defaults to "cuda"')
@click.option('--fp16', 'use_fp16', is_flag=True, help='Use fp16 precision for 2x faster inference.')
@click.option('--resize', 'resize_to', type=int, default=None, help='Resize the image(s) & output maps to a specific size. Defaults to None (no resizing).')
@click.option('--resolution_level', type=int, default=9, help='An integer [0-9] for the resolution level for inference. \
Higher value means more tokens and the finer details will be captured, but inference can be slower. \
Defaults to 9. Note that it is irrelevant to the output size, which is always the same as the input size. \
`resolution_level` actually controls `num_tokens`. See `num_tokens` for more details.')
@click.option('--num_tokens', type=int, default=None, help='number of tokens used for inference. A integer in the (suggested) range of `[1200, 2500]`. \
`resolution_level` will be ignored if `num_tokens` is provided. Default: None')
@click.option('--threshold', type=float, default=0.03, help='Threshold for removing edges. Defaults to 0.03. Smaller value removes more edges. "inf" means no thresholding.')
@click.option('--maps', 'save_maps_', is_flag=True, help='Whether to save the output maps and fov(image, depth, mask, points, fov).')
@click.option('--glb', 'save_glb_', is_flag=True, help='Whether to save the output as a.glb file. The color will be saved as a texture.')
@click.option('--ply', 'save_ply_', is_flag=True, help='Whether to save the output as a.ply file. The color will be saved as vertex colors.')
@click.option('--show', 'show', is_flag=True, help='Whether show the output in a window. Note that this requires pyglet<2 installed as required by trimesh.')
@click.option('--npy', 'save_npy_', is_flag=True, help='Whether show the output in a window. Note that this requires pyglet<2 installed as required by trimesh.')
def main(
    input_path: str,
    fov_x_: float,
    output_path: str,
    pretrained_model_name_or_path: str,
    device_name: str,
    use_fp16: bool,
    resize_to: int,
    resolution_level: int,
    num_tokens: int,
    threshold: float,
    save_maps_: bool,
    save_glb_: bool,
    save_ply_: bool,
    show: bool,
    save_npy_:bool,
):  
    device = torch.device(device_name)

    assert input_path is not None and input_path.endswith('jsonl'),'input jsonl is not that correct'


    dataset = MoGeDataLoader(json_files=[input_path])


    dataloader = torch.utils.data.DataLoader(dataset, batch_size=4, shuffle=False, num_workers=32)

    
    model = MoGeModel.from_pretrained(pretrained_model_name_or_path).to(device).eval()


    

    
    # for image_path in (pbar := tqdm(image_paths, desc='Inference', disable=len(image_paths) <= 1)):

    #     #* prepare input
    #     image = cv2.cvtColor(cv2.imread(str(image_path)), cv2.COLOR_BGR2RGB)
    #     height, width = image.shape[:2]
    #     if resize_to is not None:
    #         height, width = min(resize_to, int(resize_to * height / width)), min(resize_to, int(resize_to * width / height))
    #         image = cv2.resize(image, (width, height), cv2.INTER_AREA)
        
        
    #     input_image, mask_np = extract_object_mask2former(processor, segmodel, Image.fromarray(image), device)
    #     image_tensor = torch.tensor(image / 255, dtype=torch.float32, device=device).permute(2, 0, 1)
    logger.warning(f'start inference with {len(dataloader)} batches')    
    
    for batch in tqdm(dataloader, desc='Inference', ):
        

        image_tensor = batch['hint']


        batch['path']

        
        #* Inference
        output = model.infer(image_tensor, fov_x=fov_x_, resolution_level=resolution_level, num_tokens=num_tokens, use_fp16=use_fp16)


        #* save 
        depth, intrinsic = output['depth'].cpu().numpy(), output['intrinsics'].cpu().numpy()
        
        for  path,d, intrin in zip(batch['path'],depth,intrinsic):
            suffix =  path.split('.')[-1]
            
            np.save(path.replace(suffix, 'npy'), d)

            fov_x, fov_y = utils3d.numpy.intrinsics_to_fov(intrin)
            with open(path.replace(suffix, 'json'), 'w') as f:
                json.dump({
                    'fov_x': round(float(np.rad2deg(fov_x)), 2),
                    'fov_y': round(float(np.rad2deg(fov_y)), 2),
                }, f)





if __name__ == '__main__':
    import multiprocessing
    multiprocessing.set_start_method('spawn')
    main()
